package com.whz.generic;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import de.taimos.totp.TOTP;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.util.regex.Pattern;

/**
 * google 2factor
 *
 * @author whz
 */
@Slf4j
@UtilityClass
public class Google2FaUtils {

    private static final String OTPAUTH_TOTP = "otpauth://totp/";
    private static final String SECRET = "?secret=";
    private static final String ISSUER = "&issuer=";
    private static final String COLON = ":";
    private static final String UTF_8 = "UTF-8";
    private static final String PLUS_SIGN = "\\+";
    private static final String REPLACEMENT = "%20";
    private static final Pattern COMPILE =
            Pattern.compile(PLUS_SIGN, Pattern.LITERAL);
    private static final Pattern COMPILED = Pattern.compile(PLUS_SIGN, Pattern.LITERAL);
    private static final Pattern PATTERN = Pattern.compile(PLUS_SIGN, Pattern.LITERAL);

    /**
     * 生成Base32字符随机密码
     *
     * @return Base32编码的随机密码
     */
    public static String generateSecretKey() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[20];
        random.nextBytes(bytes);
        Base32 base32 = new Base32();
        return base32.encodeToString(bytes);
    }

    /**
     * generate time-based 6-digits code
     *
     * @param secretKey base32编码的随机秘钥
     * @return 6数字code
     */
    public static String get2TpCode(String secretKey) {
        byte[] bytes = new Base32().decode(secretKey);
        String hexKey = Hex.encodeHexString(bytes);
        return TOTP.getOTP(hexKey);
    }

    /**
     * 生成
     *
     * @param secretKey base32编码的随机秘钥
     * @param account   用户ming
     * @param issuer    组织
     * @return google2fa QR url
     */
    public static String getGoogleAuthenticatorBarCode(String secretKey, String account, String issuer) {
        try {
            return OTPAUTH_TOTP
                   + COMPILED.matcher(URLEncoder.encode(issuer + COLON + account, UTF_8)).replaceAll(REPLACEMENT)
                   + SECRET + COMPILE.matcher(URLEncoder.encode(secretKey, UTF_8)).replaceAll(REPLACEMENT)
                   + ISSUER + PATTERN.matcher(URLEncoder.encode(issuer, UTF_8)).replaceAll(REPLACEMENT);
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * 生产二维码图片
     *
     * @param barCodeData 2fa url
     * @param filePath    生成的图片存放位置
     * @param height      图片高
     * @param width       图片宽
     */
    @SneakyThrows
    public static void createQrCode(String barCodeData, String filePath, int height, int width) {
        try (FileOutputStream out = new FileOutputStream(filePath)) {
            createQrCode(barCodeData, height, width, out);
        }
    }

    /**
     * 生成google2fa二维码
     *
     * @param barCodeData 2fa url
     * @param height      图片高
     * @param width       图片宽
     * @return 图片字节
     */
    @SneakyThrows
    public static byte[] createQrCode(String barCodeData, int height, int width) {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            createQrCode(barCodeData, height, width, out);
            return out.toByteArray();
        }
    }

    @SneakyThrows
    private void createQrCode(String barCodeData, int height, int width, OutputStream outputStream) {
        BitMatrix matrix = new MultiFormatWriter().encode(barCodeData, BarcodeFormat.QR_CODE, width, height);
        MatrixToImageWriter.writeToStream(matrix, "png", outputStream);
    }


    /**
     * 验证google2fa code
     *
     * @param secretKey 用户base32编码的秘钥
     * @param code      用户输入的6位数字验证码
     * @return true 验证成功
     */
    public static boolean check2FaCode(String secretKey, String code) {
        if (StringUtils.isEmpty(secretKey) || StringUtils.isEmpty(code)) {
            return false;
        }
        return code.equals(get2TpCode(secretKey));
    }
}